package com.mapperdb.util;

import java.beans.Transient;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Pattern;

import javax.annotation.Resource;

import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.DeleteProvider;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.InsertProvider;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.ResultType;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.SelectKey;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.annotations.Update;
import org.apache.ibatis.annotations.UpdateProvider;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import com.mapperdb.builder.annotation.ResultBuilder;
import com.mapperdb.helper.AnntHelper;
import com.mapperdb.mapping.Result;

public class MapperUtils {
	public static final String ATTR_READ_REGEX = "^(get|is)([A-Z]+)([A-Za-z0-9_]*)";

	/**
	 * 重新设置SqlSource
	 *
	 * @param ms
	 * @param sqlSource
	 */
	public static void setSqlSource(MappedStatement ms, SqlSource sqlSource) {
		MetaObject msObject = SystemMetaObject.forObject(ms);
		msObject.setValue("sqlSource", sqlSource);
	}

	public static SqlSource createSqlSource(MappedStatement ms, String xmlSql) {
		return ms.getLang().createSqlSource(ms.getConfiguration(), "<script>\n\t" + xmlSql + "</script>", null);
	}

	/**
	 * 获取返回值类型 - 实体类型
	 *
	 * @param ms
	 * @return
	 */
	public static Class<?> getGenericType(String msId) {
		Class<?> mapperClass = getMapperClass(msId);
		Type[] types = mapperClass.getGenericInterfaces();
		for (Type type : types) {
			if (type instanceof ParameterizedType) {
				ParameterizedType t = (ParameterizedType) type;
				return (Class<?>) t.getActualTypeArguments()[0];
			}
		}
		throw new RuntimeException("无法获取Mapper<T>泛型类型:" + msId);
	}

	/**
	 * 根据msId获取接口类
	 *
	 * @param msId
	 * @return
	 * @throws ClassNotFoundException
	 */
	public static Class<?> getMapperClass(String msId) {
		if (msId.indexOf(".") == -1) {
			throw new RuntimeException("当前 MappedStatement 的 id = " + msId + ",不符合 MappedStatement 的规则!");
		}
		String mapperClassName = msId.substring(0, msId.lastIndexOf("."));
		try {
			return Class.forName(mapperClassName);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException("当前 mapper 不存在:" + mapperClassName, e);
		}
	}

	public static String[] delimitedStringtoArray(String in) {
		if (in == null || in.trim().length() == 0) {
			return null;
		} else {
			return in.split(",");
		}
	}

	public static SqlCommandType getSqlCommandType(Method method) {
		Class<? extends Annotation> type = AnntHelper.getSqlAnnotationType(method);

		if (type == null) {
			type = AnntHelper.getSqlProviderAnnotationType(method);

			if (type == null) {
				return SqlCommandType.UNKNOWN;
			}

			if (type == SelectProvider.class) {
				type = Select.class;
			} else if (type == InsertProvider.class) {
				type = Insert.class;
			} else if (type == UpdateProvider.class) {
				type = Update.class;
			} else if (type == DeleteProvider.class) {
				type = Delete.class;
			}
		}

		return SqlCommandType.valueOf(type.getSimpleName().toUpperCase(Locale.ENGLISH));
	}

	public static Class<?> getReturnType(String id, Method method) {
		Class<?> returnType = method.getReturnType();
		try {
			// TODO Object 类型获取 尝试获取实际泛型类型
			if (returnType == Object.class) return MapperUtils.getGenericType(id);
		} catch (Exception e) {
			// ignore
		}

		if (void.class.equals(returnType)) { // issue #508
			ResultType rt = method.getAnnotation(ResultType.class);
			if (rt != null) {
				returnType = rt.value();
			} else {// TODO 默认返回值
				returnType = int.class;
			}
		} else if (Collection.class.isAssignableFrom(returnType)) {
			Type returnTypeParameter = method.getGenericReturnType();
			if (returnTypeParameter instanceof ParameterizedType) {
				Type[] actualTypeArguments = ((ParameterizedType) returnTypeParameter).getActualTypeArguments();
				if (actualTypeArguments != null && actualTypeArguments.length == 1) {
					returnTypeParameter = actualTypeArguments[0];
					if (returnTypeParameter instanceof Class) {
						returnType = (Class<?>) returnTypeParameter;
					} else if (returnTypeParameter instanceof ParameterizedType) { // (issue #443) actual type can be a also a parametrized type
						returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
					} else if (returnTypeParameter instanceof GenericArrayType) {
						Class<?> componentType = (Class<?>) ((GenericArrayType) returnTypeParameter).getGenericComponentType();
						returnType = Array.newInstance(componentType, 0).getClass(); // (issue #525) support List<byte[]>
					} else if (returnTypeParameter instanceof TypeVariable) {// TODO 添加 泛型类型判断
						returnType = MapperUtils.getGenericType(id);
					}
				}
			}
		} else if (method.isAnnotationPresent(MapKey.class) && Map.class.isAssignableFrom(returnType)) {
			// (issue 504) Do not look into Maps if there is not MapKey annotation
			Type returnTypeParameter = method.getGenericReturnType();
			if (returnTypeParameter instanceof ParameterizedType) {
				Type[] actualTypeArguments = ((ParameterizedType) returnTypeParameter).getActualTypeArguments();
				if (actualTypeArguments != null && actualTypeArguments.length == 2) {
					returnTypeParameter = actualTypeArguments[1];
					if (returnTypeParameter instanceof Class) {
						returnType = (Class<?>) returnTypeParameter;
					} else if (returnTypeParameter instanceof ParameterizedType) { // (issue 443) actual type can be a also a parametrized type
						returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
					}
				}
			}
		}

		return returnType;
	}

	public static Class<?> getParameterType(Method method) {
		Class<?> parameterType = null;
		Class<?>[] parameterTypes = method.getParameterTypes();
		for (int i = 0; i < parameterTypes.length; i++) {
			if (!RowBounds.class.isAssignableFrom(parameterTypes[i])
					&& !ResultHandler.class.isAssignableFrom(parameterTypes[i])) {
				if (parameterType == null) {
					parameterType = parameterTypes[i];
				} else {
					parameterType = ParamMap.class; // issue #135
				}
			}
		}
		return parameterType;
	}

	public static String getTableName(Class<?> entityClass) {
		Resource resource = entityClass.getAnnotation(Resource.class);
		String tableName = entityClass.getSimpleName();
		if (resource != null) {
			String name = resource.name();
			if (!(name == null || name.isEmpty())) {
				tableName = name;
			}
		}
		return tableName;
	}

	public static List<Result> getResults(Class<?> entityClass) {
		return ResultBuilder.use(readAttrs(entityClass)).getResults();
	}

	public static List<Method> readAttrs(Class<?> entityClass) {
		List<Method> list = new ArrayList<Method>();

		Method[] methods = entityClass.getMethods();
		// 筛选 get 属性
		for (Method m : methods) {
			if (Modifier.isStatic(m.getModifiers())// static
					|| m.isAnnotationPresent(Transient.class)// Transient
					|| m.getParameterTypes().length != 0// param
					|| m.getReturnType() == Void.TYPE// void
					|| !Pattern.matches(ATTR_READ_REGEX, m.getName())// ( get*** | is*** )
					|| AnntHelper.getResultAnnotationType(m) == null) {// Result
				continue;
			}
			list.add(m);
		}

		return list;
	}

	public static SelectKey parseSelectKey(Class<?> type) {
		List<Method> attrs = readAttrs(type);
		for (Method method : attrs) {
			SelectKey selectKey = method.getAnnotation(SelectKey.class);
			if (selectKey != null) return selectKey;
		}
		return null;
	}
}
